﻿using System;

namespace CommonFramework.FixMath
{
    public struct FMatrix33 : IEquatable<FMatrix33>
    {
        public static readonly FMatrix33 Zero =
            new FMatrix33(FVector3.Zero, FVector3.Zero, FVector3.Zero);

        public static readonly FMatrix33 Identity = new FMatrix33(
            FVector3.FromRawInt(FFloat.Precision, 0, 0),
            FVector3.FromRawInt(0, FFloat.Precision, 0),
            FVector3.FromRawInt(0, 0, FFloat.Precision));

        // mRowCol  列优先存储
        public int raw00;
        public int raw10;
        public int raw20;
        public int raw01;
        public int raw11;
        public int raw21;
        public int raw02;
        public int raw12;
        public int raw22;

        public FMatrix33(FVector3 column0, FVector3 column1, FVector3 column2)
        {
            this.raw00 = column0.RawX;
            this.raw01 = column1.RawX;
            this.raw02 = column2.RawX;
            this.raw10 = column0.RawY;
            this.raw11 = column1.RawY;
            this.raw12 = column2.RawY;
            this.raw20 = column0.RawZ;
            this.raw21 = column1.RawZ;
            this.raw22 = column2.RawZ;
        }


        public FFloat this[int row, int column]
        {
            get { return this[row + column * 3]; }
            set { this[row + column * 3] = value; }
        }

        public FFloat this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                    return FFloat.FromRawInt(this.raw00);
                    case 1:
                    return FFloat.FromRawInt(this.raw10);
                    case 2:
                    return FFloat.FromRawInt(this.raw20);
                    case 3:
                    return FFloat.FromRawInt(this.raw01);
                    case 4:
                    return FFloat.FromRawInt(this.raw11);
                    case 5:
                    return FFloat.FromRawInt(this.raw21);
                    case 6:
                    return FFloat.FromRawInt(this.raw02);
                    case 7:
                    return FFloat.FromRawInt(this.raw12);
                    case 8:
                    return FFloat.FromRawInt(this.raw22);
                    default:
                    throw new IndexOutOfRangeException("Invalid matrix index!");
                }
            }
            set
            {
                switch (index)
                {
                    case 0:
                    this.raw00 = value.RawVal();
                    break;
                    case 1:
                    this.raw10 = value.RawVal();
                    break;
                    case 2:
                    this.raw20 = value.RawVal();
                    break;
                    case 3:
                    this.raw01 = value.RawVal();
                    break;
                    case 4:
                    this.raw11 = value.RawVal();
                    break;
                    case 5:
                    this.raw21 = value.RawVal();
                    break;
                    case 6:
                    this.raw02 = value.RawVal();
                    break;
                    case 7:
                    this.raw12 = value.RawVal();
                    break;
                    case 8:
                    this.raw22 = value.RawVal();
                    break;
                    default:
                    throw new IndexOutOfRangeException("Invalid matrix index!");
                }
            }
        }

        public override int GetHashCode()
        {
            return this.GetColumn(0).GetHashCode() ^ this.GetColumn(1).GetHashCode() << 2 ^
                   this.GetColumn(2).GetHashCode() >> 2;
        }

        public override bool Equals(object other)
        {
            if (!(other is FMatrix33))
                return false;
            return this.Equals((FMatrix33)other);
        }

        public bool Equals(FMatrix33 other)
        {
            return this.GetColumn(0).Equals(other.GetColumn(0))
                   && this.GetColumn(1).Equals(other.GetColumn(1))
                   && this.GetColumn(2).Equals(other.GetColumn(2));
        }

        public static FMatrix33 operator *(FMatrix33 lhs, FMatrix33 rhs)
        {
            FMatrix33 mat;
            mat.raw00 = (int)(((long)lhs.raw00 * (long)rhs.raw00 + (long)lhs.raw01 * (long)rhs.raw10 +
                              (long)lhs.raw02 * (long)rhs.raw20) / FFloat.Precision);
            mat.raw01 = (int)(((long)lhs.raw00 * (long)rhs.raw01 + (long)lhs.raw01 * (long)rhs.raw11 +
                              (long)lhs.raw02 * (long)rhs.raw21) / FFloat.Precision);
            mat.raw02 = (int)(((long)lhs.raw00 * (long)rhs.raw02 + (long)lhs.raw01 * (long)rhs.raw12 +
                              (long)lhs.raw02 * (long)rhs.raw22) / FFloat.Precision);
            mat.raw10 = (int)(((long)lhs.raw10 * (long)rhs.raw00 + (long)lhs.raw11 * (long)rhs.raw10 +
                              (long)lhs.raw12 * (long)rhs.raw20) / FFloat.Precision);
            mat.raw11 = (int)(((long)lhs.raw10 * (long)rhs.raw01 + (long)lhs.raw11 * (long)rhs.raw11 +
                              (long)lhs.raw12 * (long)rhs.raw21) / FFloat.Precision);
            mat.raw12 = (int)(((long)lhs.raw10 * (long)rhs.raw02 + (long)lhs.raw11 * (long)rhs.raw12 +
                              (long)lhs.raw12 * (long)rhs.raw22) / FFloat.Precision);
            mat.raw20 = (int)(((long)lhs.raw20 * (long)rhs.raw00 + (long)lhs.raw21 * (long)rhs.raw10 +
                              (long)lhs.raw22 * (long)rhs.raw20) / FFloat.Precision);
            mat.raw21 = (int)(((long)lhs.raw20 * (long)rhs.raw01 + (long)lhs.raw21 * (long)rhs.raw11 +
                              (long)lhs.raw22 * (long)rhs.raw21) / FFloat.Precision);
            mat.raw22 = (int)(((long)lhs.raw20 * (long)rhs.raw02 + (long)lhs.raw21 * (long)rhs.raw12 +
                              (long)lhs.raw22 * (long)rhs.raw22) / FFloat.Precision);
            return mat;
        }

        public static FVector3 operator *(FMatrix33 lhs, FVector3 vector3)
        {
            FVector3 vec;
            var rawX = (((long)lhs.raw00 * vector3.X.ToLong() + (long)lhs.raw01 * vector3.Y.ToLong() +
                             (long)lhs.raw02 * vector3.Z.ToLong()) / FFloat.Precision);
            var rawY = (int)(((long)lhs.raw10 * vector3.X.ToLong() + (long)lhs.raw11 * vector3.Y.ToLong() +
                              (long)lhs.raw12 * vector3.Z.ToLong()) / FFloat.Precision);
            var rawZ = (int)(((long)lhs.raw20 * vector3.X.ToLong() + (long)lhs.raw21 * (long)vector3.Y.ToLong() +
                             (long)lhs.raw22 * vector3.Z.ToLong()) / FFloat.Precision);
            return FVector3.FromRawLong(rawX, rawY, rawZ);
        }

        public static bool operator ==(FMatrix33 lhs, FMatrix33 rhs)
        {
            return lhs.GetColumn(0) == rhs.GetColumn(0) && lhs.GetColumn(1) == rhs.GetColumn(1) &&
                   lhs.GetColumn(2) == rhs.GetColumn(2);
        }

        public static bool operator !=(FMatrix33 lhs, FMatrix33 rhs)
        {
            return !(lhs == rhs);
        }

        /// <summary>
        ///   <para>Get a column of the matrix.</para>
        /// </summary>
        /// <param name="index"></param>
        public FVector3 GetColumn(int index)
        {
            switch (index)
            {
                case 0:
                return FVector3.FromRawInt(this.raw00, this.raw10, this.raw20);
                case 1:
                return FVector3.FromRawInt(this.raw01, this.raw11, this.raw21);
                case 2:
                return FVector3.FromRawInt(this.raw02, this.raw12, this.raw22);
                default:
                throw new IndexOutOfRangeException("Invalid column index!");
            }
        }

        /// <summary>
        ///   <para>Returns a row of the matrix.</para>
        /// </summary>
        /// <param name="index"></param>
        public FVector3 GetRow(int index)
        {
            switch (index)
            {
                case 0:
                return FVector3.FromRawInt(this.raw00, this.raw01, this.raw02);
                case 1:
                return FVector3.FromRawInt(this.raw10, this.raw11, this.raw12);
                case 2:
                return FVector3.FromRawInt(this.raw20, this.raw21, this.raw22);
                default:
                throw new IndexOutOfRangeException("Invalid row index!");
            }
        }

        /// <summary>
        ///   <para>Sets a column of the matrix.</para>
        /// </summary>
        /// <param name="index"></param>
        /// <param name="column"></param>
        public void SetColumn(int index, FVector3 column)
        {
            this[0, index] = column.X;
            this[1, index] = column.Y;
            this[2, index] = column.Z;
        }

        /// <summary>
        ///   <para>Sets a row of the matrix.</para>
        /// </summary>
        /// <param name="index"></param>
        /// <param name="row"></param>
        public void SetRow(int index, FVector3 row)
        {
            this[index, 0] = row.X;
            this[index, 1] = row.Y;
            this[index, 2] = row.Z;
        }
    }
}